﻿// -----------------------------------------------------------------------
// <copyright file="FourierOperations.cs" company="Image Processing Course Project">
// Created 21/11/2011
// </copyright>
// -----------------------------------------------------------------------

namespace ImageProcessing.IntermediateClasses
{
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using FrequencyDomain;
    using ImageProcessing.Utilities;
    using MathWorks.MATLAB.NET.Arrays;
    using MathWorks.MATLAB.NET.Utility;

    /// <summary>
    /// Class for Fourier Transform Operation Steps
    /// </summary>
    public class FourierOperations
    {
        /// <summary>
        /// Half image width
        /// </summary>
        private double centerWidth;

        /// <summary>
        /// Half image height
        /// </summary>
        private double centerHeight;

        /// <summary>
        /// Original Image to work on
        /// </summary>
        private Bitmap originalImage;

        /// <summary>
        /// Color Buffer to carry magnitudes of all 3 colors
        /// </summary>
        private Colour.ColorsStruct[,] magnitudes;

        /// <summary>
        /// Bitmap Image after reverse fourier transform
        /// </summary>
        private Bitmap resultingBuffer;

        /// <summary>
        /// Instance of MatLab Class FourierTransformClass
        /// </summary>
        private FourierTransformClass fourier;

        /// <summary>
        /// Byte buffer carrying the image values of the color Red
        /// </summary>
        private byte[,] bufferR;

        /// <summary>
        /// Byte buffer carrying the image values of the color Green
        /// </summary>
        private byte[,] bufferG;

        /// <summary>
        /// Byte buffer carrying the image values of the color Blue
        /// </summary>
        private byte[,] bufferB;

        /// <summary>
        /// Double buffer carrying real and imaginary results of Red buffer fourier transform
        /// </summary>
        private double[,] resultedR;

        /// <summary>
        /// Double buffer carrying real and imaginary results of Green buffer fourier transform
        /// </summary>
        private double[,] resultedG;

        /// <summary>
        /// Double buffer carrying real and imaginary results of Blue buffer fourier transform
        /// </summary>
        private double[,] resultedB;

        /// <summary>
        /// Double buffer carrying the real part of the Red buffer fourier transform
        /// </summary>
        public double[,] realR;

        /// <summary>
        /// Double buffer carrying the real part of the Green buffer fourier transform
        /// </summary>
        public double[,] realG;

        /// <summary>
        /// Double buffer carrying the real part of the Blue buffer fourier transform
        /// </summary>
        public double[,] realB;

        /// <summary>
        /// Double buffer carrying the imaginary part of the Red buffer fourier transform
        /// </summary>
        public double[,] imaginaryR;

        /// <summary>
        /// Double buffer carrying the imaginary part of the Green buffer fourier transform
        /// </summary>
        public double[,] imaginaryG;

        /// <summary>
        /// Double buffer carrying the imaginary part of the Blue buffer fourier transform
        /// </summary>
        public double[,] imaginaryB;
        
        /// <summary>
        /// Double buffer carrying the logMagnitudes of the Red buffer fourier transform
        /// </summary>
        private double[,] magnitudeR;

        /// <summary>
        /// Double buffer carrying the logMagnitudes of the Green buffer fourier transform
        /// </summary>
        private double[,] magnitudeG;

        /// <summary>
        /// Double buffer carrying the logMagnitudes of the Blue buffer fourier transform
        /// </summary>
        private double[,] magnitudeB;

        /// <summary>
        /// Initializes a new instance of the FourierOperations class
        /// </summary>
        /// <param name="original">Bitmap original image</param>
        public FourierOperations(Bitmap original)
        {
            try
            {
                this.originalImage = original;
                this.centerWidth = this.originalImage.Width / 2;
                this.centerHeight = this.originalImage.Height / 2;
                this.fourier = new FourierTransformClass();
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Ordered calling of buffer filling functions and fourier frequency domain transform operations
        /// </summary>
        public void Transform()
        {
            try
            {
                this.FillOriginalImageBuffer();
                this.FrequencyTransform();
                this.FillRealAndImaginaryBuffers();
                this.FillMagnitudeBuffers();
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Converting red magnitude buffer into Bitmap image
        /// </summary>
        /// <returns>Bitmap image of red magnitude</returns>
        public Bitmap RedMagnitude()
        {
            try
            {
                Bitmap redMagnitude = new Bitmap(this.originalImage.Width, this.originalImage.Height);

                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        redMagnitude.SetPixel(j, i, Color.FromArgb(this.magnitudes[j, i].Red, 0, 0));
                    }
                }

                return redMagnitude;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Converting green magnitude buffer into Bitmap image
        /// </summary>
        /// <returns>Bitmap image of green magnitude</returns>
        public Bitmap GreenMagnitude()
        {
            try
            {
                Bitmap greenMagnitude = new Bitmap(this.originalImage.Width, this.originalImage.Height);

                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        greenMagnitude.SetPixel(j, i, Color.FromArgb(0, this.magnitudes[j, i].Green, 0));
                    }
                }

                return greenMagnitude;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Converting blue magnitude buffer into Bitmap image
        /// </summary>
        /// <returns>Bitmap image of blue magnitude</returns>
        public Bitmap BlueMagnitude()
        {
            try
            {
                Bitmap blueMagnitude = new Bitmap(this.originalImage.Width, this.originalImage.Height);

                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        blueMagnitude.SetPixel(j, i, Color.FromArgb(0, 0, this.magnitudes[j, i].Blue));
                    }
                }

                return blueMagnitude;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Converts image from frequency domain into spatial domain using MatLab function ReverseFourier
        /// </summary>
        /// <returns>Bitmap image</returns>
        public Bitmap ReverseFourier()
        {
            try
            {
                MWNumericArray tempR = (MWNumericArray)this.fourier.ReverseFourier((MWNumericArray)this.realR, (MWNumericArray)this.imaginaryR);
                MWNumericArray tempG = (MWNumericArray)this.fourier.ReverseFourier((MWNumericArray)this.realG, (MWNumericArray)this.imaginaryG);
                MWNumericArray tempB = (MWNumericArray)this.fourier.ReverseFourier((MWNumericArray)this.realB, (MWNumericArray)this.imaginaryB);

                this.resultedR = (double[,])tempR.ToArray();
                this.resultedG = (double[,])tempG.ToArray();
                this.resultedB = (double[,])tempB.ToArray();

                this.magnitudes = new Colour.ColorsStruct[this.originalImage.Width, this.originalImage.Height];
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        this.magnitudes[j, i].Red = (int)this.resultedR[j, i];
                        this.magnitudes[j, i].Green = (int)this.resultedG[j, i];
                        this.magnitudes[j, i].Blue = (int)this.resultedB[j, i];
                    }
                }

                PostProcessing normalization = new PostProcessing();
                this.magnitudes = normalization.Normalization(this.magnitudes, this.originalImage.Width, this.originalImage.Height);

                this.resultingBuffer = new Bitmap(this.originalImage.Width, this.originalImage.Height);

                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        this.resultingBuffer.SetPixel(j, i, Color.FromArgb((int)this.magnitudes[j, i].Red, (int)this.magnitudes[j, i].Green, (int)this.magnitudes[j, i].Blue));
                    }
                }

                return this.resultingBuffer;
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Ideal low pass filter
        /// </summary>
        /// <param name="distance">Double distance from orgin input value</param>
        public void IdealLowPass(double distance)
        {
            double distanceFromOrigin;
            try
            {
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        distanceFromOrigin = Math.Sqrt(Math.Pow((double)(this.centerWidth - j), 2.0) + Math.Pow((double)(this.centerHeight - i), 2.0));
                        if (distanceFromOrigin > distance)
                        {
                            this.realR[j, i] = 0;
                            this.realG[j, i] = 0;
                            this.realB[j, i] = 0;

                            this.imaginaryR[j, i] = 0;
                            this.imaginaryG[j, i] = 0;
                            this.imaginaryB[j, i] = 0;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Ideal high pass filter
        /// </summary>
        /// <param name="distance">Double distance from orgin input value</param>
        public void IdealHighPass(double distance)
        {
            double distanceFromOrigin;
            try
            {
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        distanceFromOrigin = Math.Sqrt(Math.Pow((double)(this.centerWidth - j), 2.0) + Math.Pow((double)(this.centerHeight - i), 2.0));

                        if (distanceFromOrigin <= distance)
                        {
                            this.realR[j, i] = 0;
                            this.realG[j, i] = 0;
                            this.realB[j, i] = 0;
                            this.imaginaryR[j, i] = 0;
                            this.imaginaryG[j, i] = 0;
                            this.imaginaryB[j, i] = 0;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Butterworth Low Pass Filter
        /// </summary>
        /// <param name="distance">Double distance from orgin input value</param>
        /// <param name="butterworthN">Filter order</param>
        public void ButterworthLowPass(double distance, int butterworthN)
        {
            double distanceFromOrigin;
            try
            {
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        distanceFromOrigin = Math.Sqrt(Math.Pow((double)(this.centerWidth - j), 2.0) + Math.Pow((double)(this.centerHeight - i), 2.0));

                        this.realR[j, i] *= 1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN));
                        this.realG[j, i] *= 1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN));
                        this.realB[j, i] *= 1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN));

                        this.imaginaryR[j, i] *= 1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN));
                        this.imaginaryG[j, i] *= 1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN));
                        this.imaginaryB[j, i] *= 1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN));
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Butterworth High Pass Filter
        /// </summary>
        /// <param name="distance">Double distance from orgin input value</param>
        /// <param name="butterworthN">Filter order</param>
        public void ButterworthHighPass(double distance, int butterworthN)
        {
            double distanceFromOrigin;
            try
            {
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        distanceFromOrigin = Math.Sqrt(Math.Pow((double)(this.centerWidth - j), 2.0) + Math.Pow((double)(this.centerHeight - i), 2.0));

                        this.realR[j, i] *= 1 - (1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN)));
                        this.realG[j, i] *= 1 - (1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN)));
                        this.realB[j, i] *= 1 - (1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN)));

                        this.imaginaryR[j, i] *= 1 - (1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN)));
                        this.imaginaryG[j, i] *= 1 - (1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN)));
                        this.imaginaryB[j, i] *= 1 - (1 / (1 + Math.Pow((distanceFromOrigin / distance), 2 * butterworthN)));
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Gaussian Low Pass Filter
        /// </summary>
        /// <param name="distance">Double distance from orgin input value</param>
        public void GaussianLowPass(double distance)
        {
            double distanceFromOrigin;
            double exponentialValue;
            try
            {
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        distanceFromOrigin = Math.Sqrt(Math.Pow((double)(this.centerWidth - j), 2.0) + Math.Pow((double)(this.centerHeight - i), 2.0));
                        exponentialValue = Math.Exp(-0.5 * Math.Pow(distanceFromOrigin, 2) / Math.Pow(distance, 2));

                        this.realR[j, i] *= exponentialValue;
                        this.realG[j, i] *= exponentialValue;
                        this.realB[j, i] *= exponentialValue;
                        this.imaginaryR[j, i] *= exponentialValue;
                        this.imaginaryG[j, i] *= exponentialValue;
                        this.imaginaryB[j, i] *= exponentialValue;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Gaussian High Pass Filter
        /// </summary>
        /// <param name="distance">Double distance from orgin input value</param>
        public void GaussianHighPass(double distance)
        {
            double distanceFromOrigin;
            double exponentialValue;
            try
            {
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        distanceFromOrigin = Math.Sqrt(Math.Pow((double)(this.centerWidth - j), 2.0) + Math.Pow((double)(this.centerHeight - i), 2.0));
                        exponentialValue = Math.Exp(-0.5 * Math.Pow(distanceFromOrigin, 2) / Math.Pow(distance, 2));

                        this.realR[j, i] *= 1 - exponentialValue;
                        this.realG[j, i] *= 1 - exponentialValue;
                        this.realB[j, i] *= 1 - exponentialValue;
                        this.imaginaryR[j, i] *= 1 - exponentialValue;
                        this.imaginaryG[j, i] *= 1 - exponentialValue;
                        this.imaginaryB[j, i] *= 1 - exponentialValue;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Filling red, green and blur buffers from the original image
        /// </summary>
        private void FillOriginalImageBuffer()
        {
            try
            {
                this.bufferR = new byte[this.originalImage.Width, this.originalImage.Height];
                this.bufferG = new byte[this.originalImage.Width, this.originalImage.Height];
                this.bufferB = new byte[this.originalImage.Width, this.originalImage.Height];
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        this.bufferR[j, i] = this.originalImage.GetPixel(j, i).R;
                        this.bufferG[j, i] = this.originalImage.GetPixel(j, i).G;
                        this.bufferB[j, i] = this.originalImage.GetPixel(j, i).B;
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Calling MatLab FourierTransform function to convert image into frequency domain
        /// </summary>
        private void FrequencyTransform()
        {
            try
            {
                MWNumericArray tempR = (MWNumericArray)this.fourier.FourierTransform((MWNumericArray)this.bufferR);
                MWNumericArray tempG = (MWNumericArray)this.fourier.FourierTransform((MWNumericArray)this.bufferG);
                MWNumericArray tempB = (MWNumericArray)this.fourier.FourierTransform((MWNumericArray)this.bufferB);

                this.resultedR = (double[,])tempR.ToArray();
                this.resultedG = (double[,])tempG.ToArray();
                this.resultedB = (double[,])tempB.ToArray();
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Spliting the returned buffer from fourier transform into real and imaginary buffers
        /// </summary>
        private void FillRealAndImaginaryBuffers()
        {
            try
            {
                this.realR = new double[this.originalImage.Width, this.originalImage.Height];
                this.realG = new double[this.originalImage.Width, this.originalImage.Height];
                this.realB = new double[this.originalImage.Width, this.originalImage.Height];
                this.imaginaryR = new double[this.originalImage.Width, this.originalImage.Height];
                this.imaginaryG = new double[this.originalImage.Width, this.originalImage.Height];
                this.imaginaryB = new double[this.originalImage.Width, this.originalImage.Height];

                for (int i = 0; i < this.originalImage.Width; i++)
                {
                    for (int j = 0; j < this.originalImage.Height; j++)
                    {
                        this.realR[i, j] = this.resultedR[i, j];
                        this.realG[i, j] = this.resultedG[i, j];
                        this.realB[i, j] = this.resultedB[i, j];
                    }

                    for (int j = this.originalImage.Height; j < this.originalImage.Height * 2; j++)
                    {
                        this.imaginaryR[i, j - this.originalImage.Height] = this.resultedR[i, j];
                        this.imaginaryG[i, j - this.originalImage.Height] = this.resultedG[i, j];
                        this.imaginaryB[i, j - this.originalImage.Height] = this.resultedB[i, j];
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }

        /// <summary>
        /// Calculating logMagnitude and filling magnitudes buffers plus magnitudes buffer normalization
        /// </summary>
        private void FillMagnitudeBuffers()
        {
            try
            {
                this.magnitudeR = new double[this.originalImage.Width, this.originalImage.Height];
                this.magnitudeG = new double[this.originalImage.Width, this.originalImage.Height];
                this.magnitudeB = new double[this.originalImage.Width, this.originalImage.Height];

                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        this.magnitudeR[j, i] = Math.Log(Math.Sqrt(Math.Pow(this.realR[j, i], 2) + Math.Pow(this.imaginaryR[j, i], 2)) + 0.001);
                        this.magnitudeG[j, i] = Math.Log(Math.Sqrt(Math.Pow(this.realG[j, i], 2) + Math.Pow(this.imaginaryG[j, i], 2)) + 0.001);
                        this.magnitudeB[j, i] = Math.Log(Math.Sqrt(Math.Pow(this.realB[j, i], 2) + Math.Pow(this.imaginaryB[j, i], 2)) + 0.001);
                    }
                }

                this.magnitudes = new Colour.ColorsStruct[this.originalImage.Width, this.originalImage.Height];
                for (int i = 0; i < this.originalImage.Height; i++)
                {
                    for (int j = 0; j < this.originalImage.Width; j++)
                    {
                        this.magnitudes[j, i].Red = (int)this.magnitudeR[j, i];
                        this.magnitudes[j, i].Green = (int)this.magnitudeG[j, i];
                        this.magnitudes[j, i].Blue = (int)this.magnitudeB[j, i];
                    }
                }

                PostProcessing normalization = new PostProcessing();
                this.magnitudes = normalization.Normalization(this.magnitudes, this.originalImage.Width, this.originalImage.Height);
            }
            catch (Exception ex)
            {
                Logger.LogException(ex);
                throw;
            }
        }
    }
}
